
package com.shiku.imserver;


import com.shiku.imserver.service.IMBeanUtils;

import java.io.IOException;
import java.lang.management.ManagementFactory;
import java.lang.management.RuntimeMXBean;
import java.net.InetSocketAddress;
import java.net.StandardSocketOptions;
import java.nio.channels.AsynchronousChannelGroup;
import java.nio.channels.AsynchronousServerSocketChannel;
import java.nio.channels.AsynchronousSocketChannel;
import java.nio.channels.CompletionHandler;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.concurrent.TimeUnit;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.tio.core.Node;
import org.tio.server.TioServer;
import org.tio.utils.date.DateUtils;
import org.tio.utils.hutool.StrUtil;


public class ShikuTioServer
        extends TioServer {
    private static Logger log = LoggerFactory.getLogger(ShikuTioServer.class);

    private Node serverNode;

    protected ImServerGroupContext serverGroupContext;

    protected AsynchronousServerSocketChannel serverSocketChannel;

    private AsynchronousServerSocketChannel wsServerSocketChannel;

    protected AsynchronousChannelGroup channelGroup = null;


    public ShikuTioServer(ImServerGroupContext serverGroupContext) {

        super(serverGroupContext);

        this.serverGroupContext = serverGroupContext;

    }


    @Override
    public void start(String serverIp, int serverPort) throws IOException {

        long start = System.currentTimeMillis();

        this.serverNode = new Node(serverIp, serverPort);

        this.channelGroup = AsynchronousChannelGroup.withThreadPool(this.serverGroupContext.groupExecutor);

        this.serverSocketChannel = AsynchronousServerSocketChannel.open(this.channelGroup);


        this.serverSocketChannel.setOption(StandardSocketOptions.SO_REUSEADDR, Boolean.valueOf(true));

        this.serverSocketChannel.setOption(StandardSocketOptions.SO_RCVBUF, Integer.valueOf(65536));


        InetSocketAddress listenAddress = null;


        if (StrUtil.isBlank(serverIp)) {

            listenAddress = new InetSocketAddress(serverPort);

        } else {

            listenAddress = new InetSocketAddress(serverIp, serverPort);

        }


        this.serverSocketChannel.bind(listenAddress, 0);


        this.wsServerSocketChannel = AsynchronousServerSocketChannel.open(this.channelGroup);


        this.wsServerSocketChannel.setOption(StandardSocketOptions.SO_REUSEADDR, Boolean.valueOf(true));

        this.wsServerSocketChannel.setOption(StandardSocketOptions.SO_RCVBUF, Integer.valueOf(65536));


        Integer wsPort = IMBeanUtils.getImconfig().getWsServerConfig().getBindPort();


        this.wsServerSocketChannel.bind(new InetSocketAddress(wsPort.intValue()), 0);


        ShikuAcceptCompletionHandler acceptCompletionHandler = new ShikuAcceptCompletionHandler(this.serverSocketChannel, (byte) 1);


        ShikuAcceptCompletionHandler wsacceptCompletionHandler = new ShikuAcceptCompletionHandler(this.wsServerSocketChannel, (byte) 2);


        this.serverSocketChannel.accept(this, (CompletionHandler<AsynchronousSocketChannel, ? super ShikuTioServer>) acceptCompletionHandler);


        this.wsServerSocketChannel.accept(this, (CompletionHandler<AsynchronousSocketChannel, ? super ShikuTioServer>) wsacceptCompletionHandler);


        if (IMBeanUtils.getImconfig().isWebsocketSsl()) {

            try {

                this.serverGroupContext.useSsl(IMBeanUtils.getImconfig().getKeyStoreFile(),
                        IMBeanUtils.getImconfig().getKeyStoreFile(),
                        IMBeanUtils.getImconfig().getSslPassword());

            } catch (Exception e) {

                log.error(e.getMessage(), e);

            }

        }


        this.serverGroupContext.startTime = System.currentTimeMillis();


        IMBeanUtils.getBeanManager().startupAfter();


        String baseStr = "|----------------------------------------------------------------------------------------|";

        int baseLen = baseStr.length();

        StackTraceElement[] ses = Thread.currentThread().getStackTrace();

        StackTraceElement se = ses[ses.length - 1];

        int xxLen = 18;

        int aaLen = baseLen - 3;

        List<String> infoList = new ArrayList<>();


        infoList.add(StrUtil.fillAfter("GroupContext name", ' ', xxLen) + "| " + this.serverGroupContext.getName());

        infoList.add(StrUtil.fillAfter("Started at", ' ', xxLen) + "| " + DateUtils.formatDateTime(new Date()));

        infoList.add(StrUtil.fillAfter("Listen on", ' ', xxLen) + "| " + this.serverNode);

        infoList.add(StrUtil.fillAfter("Main Class", ' ', xxLen) + "| " + se.getClassName());


        try {

            RuntimeMXBean runtimeMxBean = ManagementFactory.getRuntimeMXBean();

            String runtimeName = runtimeMxBean.getName();

            String pid = runtimeName.split("@")[0];

            long startTime = runtimeMxBean.getStartTime();

            long startCost = System.currentTimeMillis() - startTime;

            infoList.add(StrUtil.fillAfter("Jvm start time", ' ', xxLen) + "| " + startCost + " ms");

            infoList.add(StrUtil.fillAfter("Tio start time", ' ', xxLen) + "| " + (System.currentTimeMillis() - start) + " ms");

            infoList.add(StrUtil.fillAfter("Pid", ' ', xxLen) + "| " + pid);

        } catch (Exception exception) {
        }


        String printStr = "\r\n" + baseStr + "\r\n";


        for (String string : infoList) {

            printStr = printStr + "| " + StrUtil.fillAfter(string, ' ', aaLen) + "|\r\n";

        }

        printStr = printStr + baseStr + "\r\n";

        if (log.isInfoEnabled()) {

            log.info(printStr);

        } else {

            System.out.println(printStr);

        }

    }


    @Override
    public boolean stop() {

        setWaitingStop(true);

        boolean ret = true;


        try {

            this.channelGroup.shutdownNow();

        } catch (Exception e) {

            log.error("channelGroup.shutdownNow()时报错", e);

        }


        try {

            this.serverSocketChannel.close();

        } catch (Exception e1) {

            log.error("serverSocketChannel.close()时报错", e1);

        }


        try {

            this.serverGroupContext.groupExecutor.shutdown();

        } catch (Exception e1) {

            log.error(e1.toString(), e1);

        }

        try {

            this.serverGroupContext.tioExecutor.shutdown();

        } catch (Exception e1) {

            log.error(e1.toString(), e1);

        }


        this.serverGroupContext.setStopped(true);

        try {

            ret = (ret && this.serverGroupContext.groupExecutor.awaitTermination(6000L, TimeUnit.SECONDS));

            ret = (ret && this.serverGroupContext.tioExecutor.awaitTermination(6000L, TimeUnit.SECONDS));

        } catch (InterruptedException e) {

            log.error(e.getLocalizedMessage(), e);

        }


        log.info(this.serverNode + " stopped");

        return ret;

    }


    public AsynchronousServerSocketChannel getWsServerSocketChannel() {

        return this.wsServerSocketChannel;

    }


    public void setWsServerSocketChannel(AsynchronousServerSocketChannel wsServerSocketChannel) {

        this.wsServerSocketChannel = wsServerSocketChannel;

    }

}


